package com.jzt.gateway.utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.jzt.gateway.config.JwtTokenConfig;
import com.jzt.gateway.model.AccountInfo;
import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.DefaultClaims;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.security.Key;
import java.util.Date;

/**
 * 作者：lizw <br/>
 * 创建时间：2020/01/02 15:11 <br/>
 */
@Slf4j
public class JwtTokenUtils {

    private static final String Secret_Key_Suffix = "0123456789012345678901234567890123456789012345678901234567890123";
    private static final String Token_Account_Info = "account";
    private static final ObjectMapper MAPPER = new ObjectMapper();

    /**
     * 创建Token，并将Token写入Redis
     */
    public static String createToken(AccountInfo accountInfo, JwtTokenConfig tokenConfig) throws Exception {
        //获取当前时间戳
        long now = System.currentTimeMillis();
        //存放过期时间
        Date expiration = new Date(now + tokenConfig.getTokenValidity().toMillis());
        // 优化过期时间
        if (StringUtils.isNotBlank(tokenConfig.getHoursInDay())) {
            String tmp = DateTimeUtils.formatToString(expiration, DateTimeUtils.yyyy_MM_dd);
            try {
                expiration = DateTimeUtils.parseDate(tmp.trim() + " " + tokenConfig.getHoursInDay().trim(), DateTimeUtils.yyyy_MM_dd_HH_mm_ss);
            } catch (Exception e) {
                log.warn("TokenConfig.hoursInDay配置错误", e);
            }
            if (expiration.getTime() <= now) {
                expiration = DateTimeUtils.addDays(expiration, 1);
            }
        }
        //创建Token令牌 - iss（签发者）, aud（接收方）, sub（面向的用户）,exp（过期时间戳）, iat（签发时间）, jti（JWT ID）
        DefaultClaims claims = new DefaultClaims();
        claims.setIssuer(tokenConfig.getIssuer());
        claims.setAudience(tokenConfig.getAudience());
        claims.setSubject(accountInfo.getLoginName());
        claims.setExpiration(expiration);
        claims.setIssuedAt(new Date());
        claims.setId(String.valueOf(SnowFlake.SNOW_FLAKE.nextId()));
        claims.put(Token_Account_Info, MAPPER.writeValueAsString(accountInfo));
        // 签名私钥
        Key key = Keys.hmacShaKeyFor((tokenConfig.getSecretKey() + Secret_Key_Suffix).getBytes());
        return Jwts.builder()
                .setClaims(claims)
                .signWith(key, SignatureAlgorithm.HS512)
                .compact();
    }

    /**
     * 校验Token，校验失败抛出异常
     */
    public static Jws<Claims> getClaimsJws(String token, JwtTokenConfig tokenConfig) throws JwtException {
        if (StringUtils.isBlank(token)) {
            throw new RuntimeException("Token不存在");
        }
        String[] strArray = token.split("\\.");
        if (strArray.length != 3) {
            throw new MalformedJwtException("Token格式不正确");
        }
        Key key = Keys.hmacShaKeyFor((tokenConfig.getSecretKey() + Secret_Key_Suffix).getBytes());
        try {
            //通过密钥验证Token
            return Jwts.parserBuilder().setSigningKey(key)
                    .requireIssuer(tokenConfig.getIssuer())
                    .requireAudience(tokenConfig.getAudience())
                    .build()
                    .parseClaimsJws(token);
        } catch (SignatureException e) {
            // 签名异常
            throw new JwtException("Token签名异常", e);
        } catch (MalformedJwtException e) {
            // JWT格式错误
            throw new JwtException("Token格式错误", e);
        } catch (ExpiredJwtException e) {
            // JWT过期
            throw new JwtException("Token过期", e);
        } catch (UnsupportedJwtException e) {
            // 不支持该JWT
            throw new JwtException("不支持该Token", e);
        } catch (IllegalArgumentException e) {
            // 参数错误异常
            throw new JwtException("Token参数错误异常", e);
        }
    }

    public static AccountInfo getAccountInfo(String token, JwtTokenConfig tokenConfig) throws JwtException {
        Jws<Claims> jws = getClaimsJws(token, tokenConfig);
        return getAccountInfo(jws);
    }

    public static AccountInfo getAccountInfo(Jws<Claims> jws) {
        if (jws == null || jws.getBody() == null) {
            throw new JwtException("Token认证失败");
        }
        Claims claims = jws.getBody();
        String account = claims.get(Token_Account_Info, String.class);
        if (StringUtils.isBlank(account)) {
            throw new JwtException("Token认证失败");
        }
        try {
            return MAPPER.readValue(account, AccountInfo.class);
        } catch (IOException e) {
            throw new JwtException("Token认证失败", e);
        }
    }

    /**
     * 生成刷新令牌
     */
    private static String createRefreshToken(String username) {
        return username + ":" + RandomUtil.randomString(256);
    }
}
